りおんクロニクル


SQLite × クリーンアーキテクチャ(DDD)|業務アプリを長期運用できる設計パターン【2026年版】

Home【2026年版】C# / .NET入門と実践ガイド|基礎・業務アプリ開発・SQLite連携まで体系的に解説

SQLiteは「軽くて速いローカルDB」として優秀ですが、 アプリ側の設計がスパゲッティだと、数年後には誰も触れなくなります。 そこで効いてくるのが、クリーンアーキテクチャ × DDD(ドメイン駆動設計)です。

この記事では、SQLiteを使った業務アプリに クリーンアーキテクチャとDDDをどう落とし込むかを、 実務目線で整理します。

この記事でわかること
・SQLite × クリーンアーキテクチャのレイヤー構成
・ドメイン層・アプリケーション層・インフラ層の役割
・RepositoryパターンとSQLite実装の分離
・DTO / Entity / Domain Model の使い分け
・MVVMとの組み合わせ方
・テスト容易性を高めるポイント

1. SQLite × クリーンアーキテクチャの全体像

まずは、典型的なレイヤー構成から整理します。

■ レイヤー構成(論理)

  1. Presentation層(UI:WPF / WinUI / Web)
  2. Application層(ユースケース・サービス)
  3. Domain層(ドメインモデル・ルール)
  4. Infrastructure層(SQLite・API・外部サービス)

ポイントは、内側(Domain・Application)が外側(Infrastructure)に依存しないこと。 SQLiteはあくまで「インフラの1つ」であり、 ドメインモデルはSQLiteの存在を知らない構造にします。

2. ドメイン層:ビジネスルールの中心

ドメイン層は、業務の「意味」を表現する層です。 ここにエンティティ・値オブジェクト・ドメインサービスが置かれます。

■ 例:顧客エンティティ

public class Customer
{
    public CustomerId Id { get; }
    public CustomerName Name { get; private set; }
    public EmailAddress? Email { get; private set; }

    public Customer(CustomerId id, CustomerName name, EmailAddress? email)
    {
        Id = id;
        Name = name;
        Email = email;
    }

    public void ChangeName(CustomerName newName)
    {
        if (newName == null) throw new ArgumentNullException(nameof(newName));
        Name = newName;
    }

    public void ChangeEmail(EmailAddress? newEmail)
    {
        Email = newEmail;
    }
}

ここではSQLiteの型(TEXT / INTEGER)やテーブル構造を一切意識しないのが重要です。

■ 値オブジェクト例

public record CustomerName
{
    public string Value { get; }

    public CustomerName(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            throw new ArgumentException("顧客名は必須です。", nameof(value));

        if (value.Length > 100)
            throw new ArgumentException("顧客名は100文字以内です。", nameof(value));

        Value = value;
    }

    public override string ToString() => Value;
}

バリデーションをドメイン側に寄せることで、UIやDBに依存しないルールになります。

3. Application層:ユースケースを表現する

Application層は、画面やAPIから見た「やりたいこと」を表現する層です。 ユースケース単位のサービス(アプリケーションサービス)を置きます。

■ 例:顧客登録ユースケース

public interface IRegisterCustomerUseCase
{
    Task ExecuteAsync(RegisterCustomerCommand command);
}

public class RegisterCustomerCommand
{
    public string Name { get; init; } = "";
    public string? Email { get; init; }
}
public class RegisterCustomerUseCase : IRegisterCustomerUseCase
{
    private readonly ICustomerRepository _repository;
    private readonly IUnitOfWork _uow;

    public RegisterCustomerUseCase(ICustomerRepository repository, IUnitOfWork uow)
    {
        _repository = repository;
        _uow = uow;
    }

    public async Task ExecuteAsync(RegisterCustomerCommand command)
    {
        var customer = new Customer(
            id: CustomerId.New(),
            name: new CustomerName(command.Name),
            email: string.IsNullOrEmpty(command.Email)
                ? null
                : new EmailAddress(command.Email)
        );

        await _repository.AddAsync(customer);
        await _uow.CommitAsync();
    }
}

Application層はドメインモデルを使ってユースケースを実現しますが、 ここでもSQLiteの存在は意識しません。

4. Infrastructure層:SQLite実装を閉じ込める

SQLiteへの具体的なアクセスは、Infrastructure層に閉じ込めます。 ここでRepositoryインターフェースの実装を行います。

■ Repositoryインターフェース(Domain or Application側)

public interface ICustomerRepository
{
    Task<Customer?> FindByIdAsync(CustomerId id);
    Task AddAsync(Customer customer);
    Task UpdateAsync(Customer customer);
}

■ SQLite実装(Infrastructure側)

public class SqliteCustomerRepository : ICustomerRepository
{
    private readonly Func<IDbConnection> _connectionFactory;

    public SqliteCustomerRepository(Func<IDbConnection> connectionFactory)
        => _connectionFactory = connectionFactory;

    public async Task<Customer?> FindByIdAsync(CustomerId id)
    {
        using var con = _connectionFactory();
        var dto = await con.QuerySingleOrDefaultAsync<CustomerDto>(
            "SELECT Id, Name, Email FROM Customers WHERE Id = @Id",
            new { Id = id.Value });

        if (dto == null) return null;

        return new Customer(
            new CustomerId(dto.Id),
            new CustomerName(dto.Name),
            string.IsNullOrEmpty(dto.Email) ? null : new EmailAddress(dto.Email)
        );
    }

    public async Task AddAsync(Customer customer)
    {
        using var con = _connectionFactory();
        await con.ExecuteAsync(
            "INSERT INTO Customers (Id, Name, Email) VALUES (@Id, @Name, @Email)",
            new
            {
                Id = customer.Id.Value,
                Name = customer.Name.Value,
                Email = customer.Email?.Value
            });
    }

    public async Task UpdateAsync(Customer customer)
    {
        using var con = _connectionFactory();
        await con.ExecuteAsync(
            "UPDATE Customers SET Name = @Name, Email = @Email WHERE Id = @Id",
            new
            {
                Id = customer.Id.Value,
                Name = customer.Name.Value,
                Email = customer.Email?.Value
            });
    }
}

ここで初めてSQLiteのテーブル構造・SQL・Dapper/EF Coreが登場します。 ドメイン層・Application層は、これらの詳細を知りません。

5. DTO / Entity / Domain Model の使い分け

SQLite × クリーンアーキテクチャでは、 「どこで何の型を使うか」が重要になります。

■ よくある3層の型

Repositoryは、DB DTO ⇔ Domain Model の変換を担当します。 Application層は、Domain Model ⇔ ViewModel/Response DTOの変換を担当します。

6. MVVMとの組み合わせ方(WPF想定)

WPF / WinUI でMVVMを使う場合、 Presentation層はViewModel → UseCase呼び出しに徹します。

■ ViewModel例

public class CustomerEditViewModel : INotifyPropertyChanged
{
    private readonly IRegisterCustomerUseCase _registerUseCase;

    public string Name { get; set; } = "";
    public string? Email { get; set; }

    public ICommand SaveCommand { get; }

    public CustomerEditViewModel(IRegisterCustomerUseCase registerUseCase)
    {
        _registerUseCase = registerUseCase;
        SaveCommand = new AsyncRelayCommand(SaveAsync);
    }

    private async Task SaveAsync()
    {
        var cmd = new RegisterCustomerCommand
        {
            Name = this.Name,
            Email = this.Email
        };

        await _registerUseCase.ExecuteAsync(cmd);
    }
}

ViewModelはユースケース(UseCase)を呼ぶだけにしておくと、 UI変更とドメインロジックが綺麗に分離されます。

7. テスト容易性を高めるポイント

クリーンアーキテクチャ × DDD の大きなメリットは、 テストがしやすくなることです。

特に、ドメイン層がSQLiteに依存していないことが、 テスト容易性に直結します。

8. 業務アプリ向けベストプラクティス

まとめ:SQLiteでも“ちゃんとしたアーキテクチャ”は必要

「とりあえずSQLiteで動けばいい」 から一歩進んで、 「10年運用できる業務アプリ」 を目指すなら、 クリーンアーキテクチャとDDDは非常に強力な武器になります。 この記事をベースに、あなたのプロジェクトに合ったレイヤー構成を設計してみてください。

前のページ  次のページ